const sleep = require("./utils");

module.exports = class RedisLock {
  /**
   * 初始化 RedisLock
   * @param {*} client
   * @param {*} options
   */
  constructor(client, options = {}) {
    if (!client) {
      throw new Error("client 不存在");
    }

    if (client.status !== "connecting") {
      throw new Error("client 未正常链接");
    }

    this.lockLeaseTime = options.lockLeaseTime || 2; // 默认锁过期时间 2 秒
    this.lockTimeout = options.lockTimeout || 5; // 默认锁超时时间 5 秒
    this.expiryMode = options.expiryMode || "EX";
    this.setMode = options.setMode || "NX";
    this.client = client;
  }

  intranetLock = async (start, key, val, expire) => {
    try {
      const result = await this.client.set(
        key,
        val,
        this.expiryMode,
        expire || this.lockLeaseTime,
        this.setMode
      );

      // 上锁成功
      if (result === "OK") {
        console.log(`${key} ${val} 上锁成功`);
        return true;
      }

      // 锁超时
      if (Math.floor((Date.now() - start) / 1000) > this.lockTimeout) {
        console.log(`${key} ${val} 上锁重试超时结束`);
        return false;
      }

      // 循环等待重试
      console.log(`${key} ${val} 等待重试`);
      await sleep(3000);
      console.log(`${key} ${val} 开始重试`);

      return this.intranetLock();
    } catch (err) {
      throw new Error(err);
    }
  };

  lock = async (key, val, expire) => {
    const start = Date.now();
    return this.intranetLock(start, key, val, expire);
  };

  unLock = async (key, val) => {
    // Lua 脚本
    const script =
      "if redis.call('get',KEYS[1]) == ARGV[1] then" +
      "   return redis.call('del',KEYS[1]) " +
      "else" +
      "   return 0 " +
      "end";

    try {
      const result = await this.client.eval(script, 1, key, val);

      if (result === 1) {
        return true;
      }

      return false;
    } catch (err) {
      throw new Error(err);
    }
  };
};
